<!--
@license
Copyright 2016 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<link rel="import" href="../paper-button/paper-button.html" />
<link rel="import" href="../paper-listbox/paper-listbox.html" />
<link rel="import" href="../paper-radio-group/paper-radio-group.html" />
<link rel="import" href="../paper-dialog/paper-dialog.html" />
<link rel="import" href="../paper-tooltip/paper-tooltip.html" />
<link rel="import" href="../tf-imports/polymer.html" />
<link rel="import" href="../tf-backend/tf-backend.html" />
<link rel="import" href="../tf-dashboard-common/dashboard-style.html" />
<link rel="import" href="../tf-dashboard-common/tf-dashboard-layout.html" />
<link rel="import" href="../tf-tensorboard/plugin-dialog.html" />
<link rel="import" href="../tf-tensorboard/registry.html" />
<link rel="import" href="tf-beholder-video.html" />
<link rel="import" href="tf-beholder-info.html" />

<dom-module id="tf-beholder-dashboard">
  <template>
    <tf-plugin-dialog id="initialDialog"></tf-plugin-dialog>
    <tf-dashboard-layout>
      <div class="sidebar" slot="sidebar">
        <template is="dom-if" if="[[_controls_disabled]]">
          <div class="sidebar-section">
            <p class="controls-disabled-message">
              Controls disabled: directory is not writeable.
            </p>
            <p class="disclaimer">
              Beholder requires write access to the log directory in order to
              communicate visualization changes to the <code>Beholder</code>
              instance in your model.
            </p>
          </div>
        </template>
        <div class="sidebar-section">
          <h3>Values</h3>
          <paper-radio-group id="valuesSelector" selected="{{_values}}">
            <paper-radio-button
              name="trainable_variables"
              disabled="[[_controls_disabled]]"
            >
              <pre>tf.trainable_variables()</pre>
            </paper-radio-button>
            <paper-radio-button
              id="option-arrays"
              name="arrays"
              disabled="[[_controls_disabled]]"
            >
              <pre>b.update(arrays=[NP_ARRAYS])</pre>
            </paper-radio-button>
            <paper-radio-button
              id="option-frames"
              name="frames"
              disabled="[[_controls_disabled]]"
            >
              <pre>b.update(frame=NP_ARRAY)</pre>
            </paper-radio-button>
          </paper-radio-group>

          <template is="dom-if" if="[[_valuesNotFrame(_values)]]">
            <paper-checkbox
              checked="{{_showAll}}"
              disabled="[[_controls_disabled]]"
              >Show all data <i>(can be resource intensive)</i></paper-checkbox
            >
          </template>
        </div>

        <template is="dom-if" if="[[_valuesNotFrame(_values)]]">
          <div class="sidebar-section">
            <h3>Mode</h3>
            <paper-radio-group id="modeSelector" selected="{{_mode}}">
              <paper-radio-button
                name="current"
                disabled="[[_controls_disabled]]"
              >
                current values
              </paper-radio-button>
              <paper-radio-button
                name="variance"
                disabled="[[_controls_disabled]]"
              >
                variance over train steps
              </paper-radio-button>
            </paper-radio-group>
            <template is="dom-if" if="[[_varianceSelected(_mode)]]">
              <h4>Variance timesteps: {{_windowSize}}</h4>
              <paper-slider
                id="windowSlider"
                value="{{_windowSize}}"
                type="number"
                step="1"
                min="2"
                max="20"
                pin="true"
                disabled="[[_controls_disabled]]"
              >
              </paper-slider>
            </template>
          </div>

          <div class="sidebar-section">
            <h3>Image scaling</h3>
            <paper-radio-group id="scalingSelector" selected="{{_scaling}}">
              <paper-radio-button
                id="option-layer"
                name="layer"
                disabled="[[_controls_disabled]]"
              >
                per section
              </paper-radio-button>
              <paper-tooltip for="option-layer" position="right">
                Black is the lowest value in that section, white is that largest
                value in that section.
              </paper-tooltip>

              <paper-radio-button
                id="option-network"
                name="network"
                disabled="[[_controls_disabled]]"
              >
                all sections
              </paper-radio-button>
              <paper-tooltip for="option-network" position="right">
                Black is the smallest value in all sections, white is the
                largest value in all sections.
              </paper-tooltip>
            </paper-radio-group>

            <div id="colormap-selection">
              <div id="colormap-selection-label">Colormap:</div>
              <paper-dropdown-menu
                no-label-float
                selected-item-label="{{_colormap}}"
                disabled="[[_controls_disabled]]"
              >
                <paper-listbox slot="dropdown-content" selected="0">
                  <paper-item>magma</paper-item>
                  <paper-item>inferno</paper-item>
                  <paper-item>plasma</paper-item>
                  <paper-item>viridis</paper-item>
                  <paper-item>grayscale</paper-item>
                </paper-listbox>
              </paper-dropdown-menu>
            </div>
          </div>
        </template>

        <div class="sidebar-section">
          <h3>Updates per second: {{_FPS}}</h3>
          <paper-slider
            id="FPSSlider"
            value="{{_FPS}}"
            type="number"
            step="1"
            min="0"
            max="30"
            pin="true"
            disabled="[[_controls_disabled]]"
          >
          </paper-slider>
        </div>

        <div class="sidebar-section">
          <div>
            <paper-button
              class="x-button"
              id="record_button"
              on-tap="_toggleRecord"
              disabled="[[_controls_disabled]]"
            >
              [[_recordText]]
            </paper-button>
          </div>
        </div>

        <div class="sidebar-section">
          <p class="disclaimer">
            Note: Beholder currently only works well on local file systems.
          </p>
        </div>
      </div>
      <div class="center" slot="center">
        <template is="dom-if" if="[[!_is_active]]">
          <div class="no-data-warning">
            <h3>No Beholder data was found.</h3>

            <p>Probable causes:</p>
            <ul>
              <li>Your script isn't running.</li>
              <li>You aren't calling <code>beholder.update()</code>.</li>
            </ul>

            <p>
              To use Beholder, import and instantiate the
              <code>Beholder</code> class, and call its
              <code>update</code> method with a <code>Session</code> argument
              after every train step:
            </p>

            <pre>
from tensorboard.plugins.beholder import Beholder
beholder = Beholder(LOG_DIRECTORY)

# inside train loop
beholder.update(
  session=sess,
  arrays=list_of_np_arrays,  # optional argument
  frame=two_dimensional_np_array,  # optional argument
)</pre
            >
            <p>
              If using <code>tf.train.MonitoredSession</code>, you can use
              <code>BeholderHook</code>:
            </p>

            <pre>
from tensorboard.plugins.beholder import BeholderHook
beholder_hook = BeholderHook(LOG_DIRECTORY)
with MonitoredSession(..., hooks=[beholder_hook]) as sess:
  sess.run(train_op)</pre
            >

            <p>
              If you think everything is set up properly, please see
              <a
                href="https://github.com/tensorflow/tensorboard/blob/master/tensorboard/plugins/beholder/README.md"
                >the README</a
              >
              for more information and consider filing an issue on GitHub.
            </p>

            <p class="disclaimer">
              Note: Beholder currently only works well on local file systems.
            </p>
          </div>
        </template>

        <template is="dom-if" if="[[_is_active]]">
          <tf-beholder-video id="video" fps="[[_FPS]]"></tf-beholder-video>

          <template is="dom-if" if="[[_valuesNotFrame(_values)]]">
            <tf-beholder-info id="info" fps="[[_FPS]]"> </tf-beholder-info>
          </template>
        </template>
      </div>
    </tf-dashboard-layout>
    <style include="dashboard-style"></style>
    <style>
      .center {
        height: 100%;
        display: flex;
        padding: 0;
      }

      .no-data-warning {
        max-width: 540px;
        margin: 80px auto 0;
      }

      paper-checkbox {
        display: block;
        padding: 4px;
      }

      paper-radio-button {
        display: flex;
        padding: 5px;

        --paper-radio-button-radio-container: {
          flex-grow: 0;
          flex-shrink: 0;
        }

        --paper-radio-button-label: {
          font-size: 13px;
          overflow: hidden;
          text-overflow: ellipsis;
        }
      }

      paper-radio-group {
        margin-top: 5px;
        width: 100%;
      }

      paper-slider {
        --paper-slider-active-color: var(--tb-orange-strong);
        --paper-slider-knob-color: var(--tb-orange-strong);
        --paper-slider-knob-start-border-color: var(--tb-orange-strong);
        --paper-slider-knob-start-color: var(--tb-orange-strong);
        --paper-slider-markers-color: var(--tb-orange-strong);
        --paper-slider-pin-color: var(--tb-orange-strong);
        --paper-slider-pin-start-color: var(--tb-orange-strong);
        flex-grow: 2;
      }

      pre {
        display: inline;
      }

      paper-button#record_button {
        color: #d32f2f;
      }

      paper-button#record_button.is-recording {
        background: #d32f2f;
        color: white;
      }

      .sidebar-section.beholder-dashboard:last-child {
        flex-grow: 0;
      }

      #colormap-selection {
        display: flex;
        margin-top: 5px;
      }

      #colormap-selection-label {
        margin-top: 13px;
      }

      #colormap-selection paper-dropdown-menu {
        margin-left: 10px;
        --paper-input-container-focus-color: var(--tb-orange-strong);
        width: 105px;
      }

      h4 {
        font-size: 14px;
        font-weight: normal;
        margin: 5px 0;
      }

      p.disclaimer {
        color: #999;
        font-style: italic;
      }

      p.controls-disabled-message {
        color: #c00;
        font-weight: bold;
      }

      .sidebar {
        font-size: 14px;
      }
    </style>
  </template>
  <script>
    'use strict';
    (function() {
      const PLUGIN_NAME = 'beholder';

      Polymer({
        is: 'tf-beholder-dashboard',

        properties: {
          _requestManager: {
            type: Object,
            value: () => new tf_backend.RequestManager(10, 0),
          },

          _isAvailable: Boolean,

          _values: {
            type: String,
            value: 'trainable_variables',
            observer: '_configChanged',
          },

          _mode: {
            type: String,
            value: 'variance',
            observer: '_configChanged',
          },

          _scaling: {
            type: String,
            value: 'layer',
            observer: '_configChanged',
          },

          _windowSize: {
            type: Number,
            value: 15,
            observer: '_configChanged',
          },

          _previousFPS: {
            type: Number,
            value: 30,
          },

          _FPS: {
            type: Number,
            value: 10,
            observer: '_configChanged',
          },

          _recordText: {
            type: String,
            value: 'start recording',
          },

          _isRecording: {
            type: Boolean,
            value: false,
            observer: '_configChanged',
          },

          _showAll: {
            type: Boolean,
            value: false,
            observer: '_configChanged',
          },

          _colormap: {
            type: String,
            value: 'magma',
            observer: '_configChanged',
          },

          _is_active: {
            type: Boolean,
            value: false,
            observer: '_configChanged',
          },

          _controls_disabled: {
            type: Boolean,
            value: false,
            observer: '_configChanged',
          },
        },

        _valuesNotFrame(values) {
          return values !== 'frames';
        },

        _varianceSelected(mode) {
          return mode === 'variance';
        },

        _configChanged() {
          // Skip recording config unless we're active and controls are enabled.
          if (!this._is_active || this._controls_disabled) {
            return;
          }

          // In case we aren't finished initializing yet.
          const properties = [
            this._values,
            this._mode,
            this._scaling,
            this._windowSize,
            this._FPS,
            this._isRecording,
            this._showAll,
            this._colormap,
          ];

          for (var property of properties) {
            if (typeof property === 'undefined' || property === '') {
              return;
            }
          }

          const url = tf_backend
            .getRouter()
            .pluginRoute(PLUGIN_NAME, '/change-config');
          const postData = {
            values: this._values,
            mode: this._mode,
            scaling: this._scaling,
            window_size: this._windowSize,
            FPS: this._FPS,
            is_recording: this._isRecording,
            show_all: this._showAll,
            colormap: this._colormap,
          };

          this._requestManager.request(url, postData);
        },

        _toggleRecord() {
          if (this._recordText == 'start recording') {
            this.set('_recordText', 'stop recording');
            this.set('_isRecording', true);
          } else {
            this.set('_recordText', 'start recording');
            this.set('_isRecording', false);
          }

          this.$.record_button.classList.toggle('is-recording');
        },

        attached: function() {
          // Check if the plugin was created
          this._requestManager
            .request(tf_backend.getRouter().pluginsListing())
            .then((plugins) => {
              if (!(PLUGIN_NAME in plugins)) {
                // The plugin was not created
                this.$.initialDialog.openNoTensorFlowDialog();
                this.set('_isAvailable', false);
              } else {
                // The plugin was created
                this.$.initialDialog.closeDialog();
                this.set('_isAvailable', true);
              }
            });
        },

        ready() {
          this.reload();
        },

        reload() {
          if (this._isAvailable) {
            const url = tf_backend
              .getRouter()
              .pluginRoute(PLUGIN_NAME, '/is-active');

            this._requestManager.request(url).then((response) => {
              this.set('_is_active', response['is_active']);
              this.set('_controls_disabled', !response['is_config_writable']);
            });
          }
        },
      });

      // TODO(#2338): Remove this, and set up the "no TF" message properly.
      // Keep this in sync with `frontend_metadata` in `beholder_plugin.py`.
      tf_tensorboard.registerDashboard({
        plugin: PLUGIN_NAME,
        elementName: 'tf-beholder-dashboard',
        shouldRemoveDom: true,
      });
    })();
  </script>
</dom-module>
